﻿using System;
using System.Collections.Generic;
using System.Data.Linq;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using LinQData.Providers;

namespace LinQProject.Repository
{
    public class DataBaseProcessBase<T>
           where T : class, new()
    {
        #region Properties
        public FarmacieDataClassesDataContext db { get; set; }
        #endregion

        #region Constructors
        public DataBaseProcessBase(FarmacieDataClassesDataContext database)
        {
            db = database;
        }
        #endregion

        #region AddEntity Region
        /// <summary>
        /// Adds a new record to the DB
        /// </summary>
        /// <param name="entity">Current Object</param>
        /// <param name="IdPropertyName">Name of the property
        ///          containing identity Column or the ID returned by 
        /// the DB</param>
        /// <returns><see cref="System.Object"/> </returns>

        public virtual object Add(T entity, string IdPropertyName)
        {
            db.GetTable<T>().InsertOnSubmit(entity);
            db.SubmitChanges();
            return entity.GetType().GetProperty(IdPropertyName).GetValue(entity, null);
        }
        #endregion

        #region GetEntityRegion
        /// <summary>
        /// Select From DB on the defined query
        /// </summary>
        /// <param name="options"><see
        /// cref="System.Data.Linq.DataLoadOptions"/></param>
        /// <param name="query">Select Query</param>
        /// <param name="from">for pagination Purposes, starting Index</param>
        /// <param name="to">for pagination Purposes, End Index</param>
        /// <returns>collection of the current type,
        /// <remarks>if "to" parameter was passed as 0,
        /// it will be defaulted to 100, you can replace it by
        /// a valued defined in the config, and another point
        /// of interest, if from > to, from will be
        /// reseted to 0.
        /// 
        /// if there is no query defined, all results will be
        /// returned, and also if there is no load data options
        /// defined, the results will contain only the entity specified
        /// with no nested data (objects) within that entity.
        /// </remarks>
        protected virtual IList<T> Get(DataLoadOptions options, Expression<Func<T, bool>> query, int from, int to)
        {
            IList<T> list = null;
            if (to == 0)
                to = 100;
            if (from > to)
                from = 0;
            if (null != options)
                db.LoadOptions = options;
            if (null == query)
                list = Enumerable.Skip(db.GetTable<T>(), from).Take(to - from).ToList();
            else
                list = db.GetTable<T>().Where(query).Skip(from).Take(to - from).ToList();

            return list;
        }
        /// <summary>
        /// Select From DB on the defined query
        /// </summary>        
        /// <param name="query">Select Query</param>
        /// <param name="from">for pagination Purposes, starting Index</param>
        /// <param name="to">for pagination Purposes, End Index</param>
        /// <returns>collection of the current type, 
        /// <remarks>if "to" parameter was passed as 0, it will be defaulted to 100, 
        ///you can replace it by a valued defined in the config, and another point of 
        /// interest, if from > to, from will be reseted to 0</remarks>
        protected virtual IList<T> Get(Expression<Func<T, bool>> query, int from, int to)
        {
            return Get(null, query, from, to);
        }
        /// <summary>
        /// Select All From DB
        /// </summary>
        /// <param name="from">for pagination Purposes, starting Index</param>
        /// <param name="to">for pagination Purposes, End Index</param>
        /// <returns>collection of the current type,
        /// <remarks>if "to" parameter was passed as 0, it will be defaulted to 100, 
        ///you can replace it by a valued defined in the config, and another point of 
        /// interest, if from > to, from will be reseted to 0</remarks>
        protected virtual IList<T> Get(int from, int to)
        {
            return Get(null, null, from, to);
        }

        public virtual List<T> GetAll()
        {
            return db.GetTable<T>().ToList();
        }

        protected virtual IList<T> GetAll(Expression<Func<T, bool>> query)
        {
            IList<T> list = null;
            if (null == query)
                list = GetAll();
            else
                list = db.GetTable<T>().Where(query).ToList();

            return list;
        }
        #endregion

        #region UpdateEntityRegion
        /// <summary>
        /// Updates Entity
        /// </summary>
        /// <param name="entity">Entity which hold the updated information</param>
        /// <param name="query">query to get the same
        /// entity from db and perform the update operation</param>
        /// <remarks>this method will do dynamic property mapping between the passed entity
        /// and the entity retrieved from DB upon the query defined,
        /// ONLY ValueTypes and strings are
        /// mapped between both entities, NO nested objects will be mapped, you have to do
        /// the objects mapping nested in your entity before calling this method</remarks>
        protected virtual void Update(T entity, Expression<Func<T, bool>> query)
        {

            T entityFromDB = db.GetTable<T>().Where(query).SingleOrDefault();
            if (null == entityFromDB)
                throw new NullReferenceException(
                    "Query Supplied to Get entity from DB is invalid, NULL value returned");

            PropertyInfo[] properties = entityFromDB.GetType().GetProperties();
            foreach (PropertyInfo property in properties)
            {
                if (null != property.GetSetMethod())
                {
                    MappingProvider.MapProperties(entity, entityFromDB, property);

                }
                db.SubmitChanges();
            }

        }

        #endregion

        #region DeleteEntityRegion
        /// <summary>
        /// Deletes the entity upon the defined query
        /// </summary>
        /// <param name="query">Delete Query</param>
        protected virtual void Delete(Expression<Func<T, bool>> query)
        {
            db.GetTable<T>().DeleteOnSubmit(
            db.GetTable<T>().Where(query).Single());
            db.SubmitChanges();
        }
        #endregion
    }
 }
